package com.abing.starry.aop;

import com.abing.api.enums.SignHeaderEnum;
import com.abing.api.utils.SignUtils;
import com.abing.starry.common.ErrorCode;
import com.abing.starry.exception.BusinessException;
import com.abing.starry.exception.ThrowUtils;
import com.abing.starry.model.entity.InterfaceInfoEntity;
import com.abing.starry.model.entity.UserEntity;
import com.abing.starry.model.entity.UserInterfaceInfoEntity;
import com.abing.starry.service.InterfaceInfoService;
import com.abing.starry.service.UserInterfaceInfoService;
import com.abing.starry.service.UserService;
import com.abing.starry.util.TokenUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.time.Duration;
import java.util.Objects;

import static com.abing.starry.constant.RedisKeyConstant.INTERFACE_COUNT_DAY;

/**
 * @Author CaptainBing
 * @Date 2024/8/29 17:50
 * @Description
 */
@Aspect
@Component
@Slf4j
@RequiredArgsConstructor
public class SimulatorInterceptor {

    private final UserService userService;

    private final UserInterfaceInfoService userInterfaceInfoService;

    private final InterfaceInfoService interfaceInfoService;

    private final StringRedisTemplate stringRedisTemplate;

    /**
     * 执行拦截
     */
    @Around("execution(* com.abing.starry.controller.SimulatorController.*(..))")
    public Object doInterceptor(ProceedingJoinPoint point) throws Throwable {

        UserEntity userEntity = checkAuthForApi();

        InterfaceInfoEntity interfaceInfoEntity = checkInterfaceExist();
        // 执行原方法
        Object result = point.proceed();
        // 输出响应日志
        log.info("simulator response:{}",result);
        // 折中处理 获取不到原先返回的json字符串 简单做一个判断
        // TODO: 寻求一种更好的方法
        String responseStr = result.toString();
        String successStr = "BaseResponse(code=200";
        if (responseStr.startsWith(successStr)) {
            UserInterfaceInfoEntity userInterfaceInfoEntity = userInterfaceInfoService.getOne(
                    Wrappers.lambdaQuery(UserInterfaceInfoEntity.class)
                            .eq(UserInterfaceInfoEntity::getUserId, userEntity.getId())
                            .eq(UserInterfaceInfoEntity::getInterfaceId, interfaceInfoEntity.getId()));
            if (Objects.isNull(userInterfaceInfoEntity)) {
                throw new BusinessException(ErrorCode.FORBIDDEN_ERROR,"未申请当前Api");
            }
            userInterfaceInfoEntity.setInvokeCount(userInterfaceInfoEntity.getInvokeCount() + 1);
            userInterfaceInfoEntity.setUsableCount(userInterfaceInfoEntity.getUsableCount() - 1);
            ThrowUtils.throwIf(!userInterfaceInfoService.updateById(userInterfaceInfoEntity), ErrorCode.SYSTEM_ERROR,"调用次数更新失败");
            // TODO lua脚本优化
            Object countObj = stringRedisTemplate.opsForHash().get(INTERFACE_COUNT_DAY, interfaceInfoEntity.getId());
            long count = Objects.nonNull(countObj) ? Long.parseLong(countObj.toString()) : 0L;
            stringRedisTemplate.opsForHash().put(INTERFACE_COUNT_DAY,interfaceInfoEntity.getId(),String.valueOf(count + 1));

        }

        return result;
    }

    /**
     * 校验api权限
     */
    private UserEntity checkAuthForApi(){

        HttpServletRequest request = TokenUtils.getRequest();

        log.info("Starry Api 请求地址:{},请求路径:{},",request.getRemoteAddr(), request.getRequestURI());
        String accessKeyHeader = request.getHeader(SignHeaderEnum.X_ACCESS_KEY.getText());
        String nonceHeader = request.getHeader(SignHeaderEnum.X_NONCE.getText());
        String timestampHeader = request.getHeader(SignHeaderEnum.X_TIMESTAMP.getText());
        String signHeader = request.getHeader(SignHeaderEnum.X_SIGN.getText());

        ThrowUtils.throwIf(StringUtils.isAnyEmpty(accessKeyHeader, nonceHeader, timestampHeader, signHeader), ErrorCode.PARAMS_ERROR, "Api 签名缺失");

        UserEntity userEntity = userService.getOne(
                Wrappers.lambdaQuery(UserEntity.class)
                        .eq(UserEntity::getAccessKey, accessKeyHeader));

        ThrowUtils.throwIf(Objects.isNull(userEntity), ErrorCode.NOT_FOUND_ERROR, "Api 签名无效");

        String secretKey = userEntity.getSecretKey();
        ThrowUtils.throwIf(StringUtils.isEmpty(secretKey), ErrorCode.NOT_FOUND_ERROR,"secretKey 不存在");

        // 校验Api签名
        String accessKey = userEntity.getAccessKey();
        ThrowUtils.throwIf(!accessKey.equals(accessKeyHeader), ErrorCode.NOT_FOUND_ERROR,"accessKey 无效");

        // 防止重放攻击 记录nonceId
        final String nonceKey = "NONCE:" + nonceHeader;
        String cacheNonce = stringRedisTemplate.opsForValue().get(nonceKey);
        ThrowUtils.throwIf(!StringUtils.isEmpty(cacheNonce), ErrorCode.NO_AUTH_ERROR,"nonce id 无效");
        stringRedisTemplate.opsForValue().set(nonceKey, nonceHeader, Duration.ofSeconds(30));

        // 校验过期时间 与当前时间不能超过5分钟
        long currentTime = System.currentTimeMillis() / 1000;
        final long fiveMinutes = 60 * 5;
        long historyTime = Long.parseLong(timestampHeader) / 1000;
        if ((currentTime - historyTime) > fiveMinutes){
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "请求已过期");
        }
        // sign 签名校验
        String genSign = SignUtils.genSign(timestampHeader, secretKey);
        ThrowUtils.throwIf(!genSign.equals(signHeader), ErrorCode.NO_AUTH_ERROR,"签名无效");
        return userEntity;
    }

    /**
     * 校验接口是否存在
     * @return
     */
    private InterfaceInfoEntity checkInterfaceExist(){
        HttpServletRequest request = TokenUtils.getRequest();
        String url = request.getRequestURL().toString();
        String method = request.getMethod().toString().toUpperCase();
        int index = !url.contains("?") ? url.length() : url.indexOf("?");
        String realUrl = url.substring(0, index);
        InterfaceInfoEntity interfaceInfoEntity = interfaceInfoService.getOne(
                Wrappers.lambdaQuery(InterfaceInfoEntity.class)
                        .eq(InterfaceInfoEntity::getUrl, realUrl)
                        .eq(InterfaceInfoEntity::getMethod, method));
        ThrowUtils.throwIf(Objects.isNull(interfaceInfoEntity), ErrorCode.NOT_FOUND_ERROR,"接口不存在");
        return interfaceInfoEntity;
    }

}
